#include "GLConnectFour.h"

GLConnectFour::GLConnectFour(QWidget *parent) :
    QGLWidget(parent) {
    setFixedSize(QSize(GL_WIDTH, GL_HEIGHT));
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
    animation.mode = Animation::NONE;
}

GLConnectFour::~GLConnectFour() {
    glDeleteLists(boardCallList, 1);
    for (int i = 0; i < NUM_COLUMNS; i++) {
        if (glIsList(columnCallLists[i])) {
            glDeleteLists(columnCallLists[i], 1);
        }
    }
    for (int i = 0; i < NUM_WINS; i++) {
        if (glIsList(winCallLists[i])) {
            glDeleteLists(winCallLists[i], 1);
        }
    }
}

void GLConnectFour::startAnimation(Animation::AnimationMode mode, int height, ConnectFour::Piece color) {
    if (mode == Animation::CLEAR_ALL) {
        animation.mode = mode;
        animation.currentY = animation.startY = 0;
        animation.endY = -LENGTH_CELL * height;
    } else {
        animation.mode = mode;
        animation.currentY = animation.startY = NUM_ROWS * LENGTH_CELL;
        animation.endY = LENGTH_CELL * height + LENGTH_CELL / 2;
        animation.positionX = LENGTH_CELL * mode + LENGTH_CELL / 2;
    }
    animation.time = 0;
    animation.color = color;
    timer->start(TIME_STEP);
}

void GLConnectFour::compileColumnList(BoardColumn * column, int index) {
    if (glIsList(columnCallLists[index])) {
        glDeleteLists(columnCallLists[index], 1);
    }
    columnCallLists[index] = glGenLists(1);
    glNewList(columnCallLists[index], GL_COMPILE_AND_EXECUTE);
    glPushMatrix();
    glTranslatef(((LENGTH_CELL / 2) + LENGTH_CELL * index), LENGTH_CELL / 2, 0.0f);
    for (int i = 0; i < column->getNumberOfPieces(); i++) {
        drawPiece(column->getPiece(i));
        glTranslatef(0.0f, LENGTH_CELL, 0.0f);
    }
    glPopMatrix();
    glEndList();
    updateGL();
}

void GLConnectFour::compileWinList(ConnectFour::Win * win, ConnectFour::Direction direction, ConnectFour::Piece piece) {
    int initialCenterX = LENGTH_CELL * win->x + LENGTH_CELL / 2 + 1;
    int initialCenterY = LENGTH_CELL * win->y + LENGTH_CELL / 2 + 1;
    float angleHeight, angleWidth;
    int finalCenterX, finalCenterY;
    if (glIsList(winCallLists[direction])) {
        glDeleteLists(winCallLists[direction], 1);
    }
    winCallLists[direction] = glGenLists(1);
    glNewList(winCallLists[direction], GL_COMPILE);
    if (piece) {
        qglColor(QColor(150, 0, 0));
    } else {
        qglColor(QColor(0, 0, 0));
    }
    glBegin(GL_QUADS);
    switch (direction) {
    case ConnectFour::VERTICAL:
        finalCenterX = initialCenterX;
        finalCenterY = LENGTH_CELL * (win->length - 1) + initialCenterY;
        glVertex2f(initialCenterX - RECT_WIDTH, initialCenterY - RECT_HEIGHT);
        glVertex2f(initialCenterX + RECT_WIDTH, initialCenterY - RECT_HEIGHT);
        glVertex2f(finalCenterX + RECT_WIDTH, finalCenterY + RECT_HEIGHT);
        glVertex2f(finalCenterX - RECT_WIDTH, finalCenterY + RECT_HEIGHT);
        break;
    case ConnectFour::HORIZONTAL:
        finalCenterX = LENGTH_CELL * (win->length - 1) + initialCenterX;
        finalCenterY = initialCenterY;
        glVertex2f(initialCenterX - RECT_HEIGHT, initialCenterY - RECT_WIDTH);
        glVertex2f(initialCenterX - RECT_HEIGHT, initialCenterY + RECT_WIDTH);
        glVertex2f(finalCenterX + RECT_HEIGHT, finalCenterY + RECT_WIDTH);
        glVertex2f(finalCenterX + RECT_HEIGHT, finalCenterY - RECT_WIDTH);
        break;
    case ConnectFour::UP_RIGHT:
        angleHeight = RECT_HEIGHT / sqrt(2);
        angleWidth = RECT_WIDTH / sqrt(2);
        finalCenterX = LENGTH_CELL * (win->length - 1) + initialCenterX;
        finalCenterY = LENGTH_CELL * (win->length - 1) + initialCenterY;
        glVertex2f(initialCenterX - angleHeight + angleWidth, initialCenterY - angleHeight - angleWidth);
        glVertex2f(initialCenterX - angleHeight - angleWidth, initialCenterY - angleHeight + angleWidth);
        glVertex2f(finalCenterX + angleHeight - angleWidth, finalCenterY + angleHeight + angleWidth);
        glVertex2f(finalCenterX + angleHeight + angleWidth, finalCenterY + angleHeight - angleWidth);
        break;
    case ConnectFour::UP_LEFT:
        angleHeight = RECT_HEIGHT / sqrt(2);
        angleWidth = RECT_WIDTH / sqrt(2);
        finalCenterX = LENGTH_CELL * (1 - win->length) + initialCenterX;
        finalCenterY = LENGTH_CELL * (win->length - 1) + initialCenterY;
        glVertex2f(initialCenterX + angleHeight + angleWidth, initialCenterY - angleHeight + angleWidth);
        glVertex2f(initialCenterX + angleHeight - angleWidth, initialCenterY - angleHeight - angleWidth);
        glVertex2f(finalCenterX - angleHeight - angleWidth, finalCenterY + angleHeight - angleWidth);
        glVertex2f(finalCenterX - angleHeight + angleWidth, finalCenterY + angleHeight + angleWidth);
        break;
    }
    glEnd();
    glEndList();
    updateGL();
}

void GLConnectFour::resetGL() {
    for (int i = 0; i < NUM_COLUMNS; i++) {
        if (glIsList(columnCallLists[i])) {
            glDeleteLists(columnCallLists[i], 1);
        }
        columnCallLists[i] = 0;
    }
    for (int i = 0; i < NUM_WINS; i++) {
        if (glIsList(winCallLists[i])) {
            glDeleteLists(winCallLists[i], 1);
        }
        winCallLists[i] = 0;
    }
    updateGL();
}

void GLConnectFour::initializeGL() {
    qglClearColor(QColor(255, 255, 255));
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glShadeModel(GL_FLAT);
    glEnable(GL_STENCIL_TEST);
    glClearStencil(0x0);
    compileBoardList();
}

void GLConnectFour::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    if (animation.mode != Animation::NONE && animation.mode != Animation::CLEAR_ALL) {
        glPushMatrix();
        glTranslatef(animation.positionX, animation.currentY, 0.0f);
        drawPiece(animation.color);
        glPopMatrix();
    }
    if (animation.mode == Animation::CLEAR_ALL) {
        glPushMatrix();
        glTranslatef(0.0f, animation.currentY, 0.0f);
    }
    for (int i = 0; i < NUM_COLUMNS; i++) {
        if (glIsList(columnCallLists[i])) {
            glCallList(columnCallLists[i]);
        }
    }
    if (animation.mode == Animation::CLEAR_ALL) {
        glPopMatrix();
    }
    glCallList(boardCallList);
    glStencilFunc(GL_ALWAYS, 0x1, 0x1);
    if (animation.mode != Animation::CLEAR_ALL) {
        for (int i = 0; i < NUM_WINS; i++) {
            if (glIsList(winCallLists[i])) {
                glCallList(winCallLists[i]);
            }
        }
    }
}

void GLConnectFour::resizeGL(int width, int height) {
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.5f, 0.5f, 0.0f);
}

void GLConnectFour::updateAnimation() {
    animation.time += TIME_STEP;
    animation.currentY = animation.startY + ACCELERATION * (animation.time * animation.time / 1000 / 1000);
    if (animation.currentY < animation.endY) {
        Animation::AnimationMode mode = animation.mode;
        animation.mode = Animation::NONE;
        timer->stop();
        emit animationFinished(mode);
    }
    updateGL();
}

void GLConnectFour::compileBoardList() {
    boardCallList = glGenLists(1);
    glNewList(boardCallList, GL_COMPILE_AND_EXECUTE);

    glStencilFunc(GL_NEVER, 0x1, 0x1);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glPushMatrix();
    glTranslatef(LENGTH_CELL / 2, LENGTH_CELL / 2, 0.0f);
    for (int i = 0; i < NUM_COLUMNS; i++) {
        glPushMatrix();
        for (int j = 0; j < NUM_ROWS; j++) {
            drawCircle(OUTER_RADIUS);
            glTranslatef(0.0f, LENGTH_CELL, 0.0f);
        }
        glPopMatrix();
        glTranslatef(LENGTH_CELL, 0.0f, 0.0f);
    }
    glPopMatrix();

    glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
    glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
    qglColor(QColor(250, 218, 94));
    glBegin(GL_QUADS);
    glVertex2f(0, 0);
    glVertex2f(0, GL_HEIGHT);
    glVertex2f(GL_WIDTH, GL_HEIGHT);
    glVertex2f(GL_WIDTH, 0);
    glEnd();

    qglColor(QColor(0, 0, 0));
    glBegin(GL_LINES);
    for (int i = 1; i < NUM_COLUMNS; i++) {
        glVertex2f(i * LENGTH_CELL, 0);
        glVertex2f(i * LENGTH_CELL, GL_HEIGHT);
    }
    for (int i = 1; i < NUM_ROWS; i++) {
        glVertex2f(0, i * LENGTH_CELL);
        glVertex2f(GL_WIDTH, i * LENGTH_CELL);
    }
    glEnd();

    glEndList();
}

void GLConnectFour::drawPiece(ConnectFour::Piece color) {
    if (color == ConnectFour::RED) {
        qglColor(QColor(150, 0, 0));
    } else {
        qglColor(QColor(0, 0, 0));
    }
    drawCircle(OUTER_RADIUS);
    if (color == ConnectFour::RED) {
        qglColor(QColor(255, 0, 0));
    } else {
        qglColor(QColor(50, 50, 50));
    }
    drawCircle(INNER_RADIUS);
}

void GLConnectFour::drawCircle(int radius) {
    glBegin(GL_TRIANGLE_FAN);
    glVertex2f(0.0f, 0.0f);
    for (float i = 0; i <= 2 * PI * (1 + 1.0 / COMPLEXITY); i += (2 * PI / COMPLEXITY)) {
        glVertex2f(cos(i) * radius, sin(i) * radius);
    }
    glEnd();
}
